#include "SockServer.h"

void clearclient() {
	int i;
	int j = 0;
	printf("client amount: %d\n", conn_amount);
	for (i = 0; i < BACKLOG; i++) {
		if (fd_A[i] > 0) {
			j++;
		}
	}
	conn_amount = j;
}

void sock_close(int* hd) {
	if (hd) {
		printf("client[%d] close\n", *hd);
		conn_amount--;
		close(*hd);
		FD_CLR(*hd, &fdsr);
		*hd = 0;
	}
}

void freeCQ(void* data) {
	if (data) {
		free(data);
	}
}

int sock_init(int port, codecFunc cf, processFunc pf) {
	//init CharQueue map
	hmap_create(&map, BACKLOG);
	//init sock
	int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd
	struct sockaddr_in server_addr; // server address information
	struct sockaddr_in client_addr; // connector's address information
	socklen_t sin_size;
	int yes = 1;
	char buf[BUF_SIZE];
	int ret;
	int i;

	if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		perror("socket error");
		exit(1);
	}

	if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
		perror("setsockopt error");
		exit(1);
	}

	server_addr.sin_family = AF_INET; // host byte order
	server_addr.sin_port = htons(port); // short, network byte order
	server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
	memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

	if (bind(sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr))
			== -1) {
		perror("bind error");
		exit(1);
	}

	if (listen(sock_fd, BACKLOG) == -1) {
		perror("listen error");
		exit(1);
	}

	printf("listen port %d\n", port);

	int maxsock;
	struct timeval tv;

	conn_amount = 0;
	sin_size = sizeof(client_addr);
	maxsock = sock_fd;
	while (1) {
		// initialize file descriptor set
		FD_ZERO(&fdsr);
		FD_SET(sock_fd, &fdsr);

		// timeout setting
		tv.tv_sec = 30;
		tv.tv_usec = 0;

		// add active connection to fd set
		for (i = 0; i < BACKLOG; i++) {
			if (fd_A[i] != 0) {
				FD_SET(fd_A[i], &fdsr);
			}
		}

		ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);
		if (ret < 0) {
			perror("select error");
			break;
		} else if (ret == 0) {
			printf("idle\n");
			continue;
		}

		// check every fd in the set
		for (i = 0; i < conn_amount; i++) {
			if (FD_ISSET(fd_A[i], &fdsr)) {
				ret = recv(fd_A[i], buf, sizeof(buf), 0);
				if (ret <= 0) { // client close
					sock_close(&fd_A[i]);
				} else { // receive data
					//printf("client[%d] send:%s\n", i, buf);
					//send(fd_A[i], buf, ret, 0);
					CB cb = (CB) malloc(sizeof(struct CharBuffer));
					memset(cb, 0x00, sizeof(struct CharBuffer));
					cb->len = ret;
					cb->pos = 0;
					cb->buf = (char*) malloc(ret + 1);
					memset(cb->buf, 0x00, ret + 1);
					memcpy(cb->buf, buf, ret);

					CQ cq = (CQ)hmap_search(map, fd_A[i]);

					while (cb->pos < cb->len) {
						(*cf)(cb, &fd_A[i], cq);
						if (cq->status == CQ_STATUS_FINISH) {
							(*pf)(cq, &fd_A[i]);
							if (cq->headBuf)
								free(cq->headBuf);
							if (cq->buf)
								free(cq->buf);
							memset(cq, 0x00, sizeof(struct CharQueue));
							cq->status = CQ_STATUS_INIT;
						}
					}
					free(cb->buf);
					free(cb);
				}
			}
		}

		// check whether a new connection comes
		if (FD_ISSET(sock_fd, &fdsr)) {
			new_fd = accept(sock_fd, (struct sockaddr *) &client_addr,
					&sin_size);
			if (new_fd <= 0) {
				perror("accept error");
				continue;
			}

			// add to fd queue
			if (conn_amount < BACKLOG) {
				fd_A[conn_amount++] = new_fd;
				printf("new connection client[%d] %s:%d\n", conn_amount,
						inet_ntoa(client_addr.sin_addr), ntohs(
								client_addr.sin_port));
				if (new_fd > maxsock)
					maxsock = new_fd;
				clearclient();

				CQ cq = (CQ) malloc(sizeof(struct CharQueue));
				memset(cq, 0x0, sizeof(struct CharQueue));
				cq->status = CQ_STATUS_INIT;
				hmap_insert(map, new_fd, cq);
			} else {
				printf("max connections arrive, exit\n");
				send(new_fd, "bye", 4, 0);
				sock_close(&new_fd);
				break;
			}
		}
	}

	// close other connections
	for (i = 0; i < BACKLOG; i++) {
		if (fd_A[i] != 0) {
			close(fd_A[i]);
		}
	}

	hmap_destroy(map, freeCQ);

	exit(0);
}

